#pragma once
#include "db.hpp"
#include "online_manager.hpp"
#include <vector>
#include <iostream>

const int BOARD_ROW = 15;
const int BOARD_COL = 15;

#define WHITE_COLOR 1
#define BLACK_COLOR 2

enum room_status
{
    START,
    OVER
};

class Room
{
public:
    Room()
    {
    }
    Room(int roomid, user_table *ut, Online_Manager *om)
        : _room_id(roomid),
          _user_table(ut),
          _online_manager(om),
          _players_num(0),
          _status(room_status::START),
          _gobang_board(BOARD_ROW, std::vector<int>(BOARD_COL, 0))
    {
        LOG(LogLevel::INFO) << "房间创建成功, 房间id: " << _room_id;
    }
    ~Room()
    {
        LOG(LogLevel::DEBUG) << "房间销毁成功, 房间id: " << _room_id;
    }

public:
    // 这是总的处理函数,外界调用该函数,然后该函数根据不同的请求类型，调用不同的处理函数,得到结果后广播给所有用户！！！
    // 可以说是个：任务派发函数！！！
    void handle_request(Json::Value &req)
    {
        Json::Value json_resp = req;

        // 1.先获取房间号,看是否与当前房间的房间号匹配
        int roomid = req["roomid"].asInt();
        if (roomid != _room_id)
        {
            json_resp["result"] = false;
            json_resp["reason"] = "房间号不匹配";
            broadcast(json_resp);
            return;
        }
        // 2.分派任务:
        std::string optype = req["optype"].asCString();
        if (optype == "put_chess") // 下棋
        {
            if (_status == OVER) // 对局已经结束,但玩家仍想下棋
            {
                json_resp["result"] = false;
                json_resp["reason"] = "对局已经结束!";
                broadcast(json_resp);
                return;
            }
            json_resp = handle_chess(req);
            int winner = json_resp["winner"].asInt();
            if (winner != -1)
            {
                int loser = winner == _whitechess_id ? _blackchess_id : _whitechess_id;
                // 更新数据库信息
                _user_table->win(winner);
                _user_table->lose(loser);
                // 游戏状态为结束:
                _status = OVER;
                broadcast(json_resp);
            }
            else
            { // 游戏继续
                broadcast(json_resp);
            }
        }
        else if (optype == "chat")
        {
            json_resp = handle_chat(req);
            broadcast(json_resp);
        }

        // 退出不属于请求,而是玩家连接断开时需要做的动作
    }

    // 处理玩家退出房间
    void handle_exit(int uid)
    {
        // 关键点: 如果是游戏进行时退出,则需要判定对方赢了,如果是游戏结束了才退出,则正常退出即可！
        if (_status == START)
        {
            Json::Value json_resp;
            json_resp["optype"] = "exit";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线,不战而胜";
            json_resp["uid"] = uid; // 退出的是谁
            // 注意谁才是胜利者:
            json_resp["winner"] = uid == _whitechess_id ? _blackchess_id : _whitechess_id;
            json_resp["roomid"] = _room_id;
            // 更新数据库信息
            _user_table->win(json_resp["winner"].asInt());
            _user_table->lose(uid);
            // 游戏状态为结束:
            _status = OVER;

            // 广播
            broadcast(json_resp);
        }

        // 房间玩家数量-1
        _players_num--;
    }

private:
    // 处理下棋请求动作
    Json::Value handle_chess(Json::Value &req)
    {
        // 1.判断当前房间的两个玩家是否都在线,任意一方不在线都是另一方胜利
        // 2.获取走走棋位置,判断当前走棋是否合理(判断是否位置已经有棋子)
        // 3.判断玩家是否胜利(出现五星连珠就是胜利)

        Json::Value json_resp = req; // 请求结果

        // 1.
        bool ret1 = _online_manager->is_in_game_room(_whitechess_id);
        if (!ret1)
        {
            json_resp["optype"] = "opponent_disconnected";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线,你获得了胜利!";
            json_resp["winner"] = _blackchess_id;
            return json_resp;
        }
        bool ret2 = _online_manager->is_in_game_room(_blackchess_id);
        if (!ret2)
        {
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线,你获得了胜利!";
            json_resp["winner"] = _whitechess_id;
            return json_resp;
        }

        // 2.
        int row = req["row"].asInt();
        int col = req["col"].asInt();
        if (row > BOARD_ROW || col > BOARD_COL || row < 0 || col < 0)
        {
            json_resp["result"] = false;
            json_resp["reason"] = "请在棋盘范围内下棋";
            return json_resp;
        }
        if (_gobang_board[row][col] != 0) // 该位置已经有棋子
        {
            json_resp["result"] = false;
            json_resp["reason"] = "当前位置已经有棋子";
            return json_resp;
        }

        // 3.
        int cur_id = req["uid"].asInt(); // 是哪个玩家下这个棋子
        bool ret3 = judge_win(cur_id, row, col);
        if (ret3) // 当前玩家胜利
        {

            json_resp["result"] = true;
            json_resp["reason"] = "五星连珠,你赢了! ! !";
            json_resp["winner"] = cur_id;
            return json_resp;
        }
        // 还未决出胜负
        json_resp["result"] = true;
        json_resp["winner"] = -1;
        return json_resp;
    }

    // 处理聊天请求动作
    Json::Value handle_chat(Json::Value &req)
    {
        Json::Value json_resp = req;
        // 检查发送的消息是否带有敏感词
        std::string msg = req["message"].asCString();
        if (!judge_msg(msg))
        {
            json_resp["result"] = false;
            json_resp["reason"] = "消息存在敏感词!";
            return json_resp;
        }

        json_resp["result"] = true;
        return json_resp;
    }

    // 将指定信息广播给房间中所有玩家(包括自己)
    void broadcast(Json::Value &resp)
    {
        // 1.对要进行广播的信息进行序列化
        std::string body;
        JSON_Util::Serialize(resp, body);

        // 2.获取房间内用户的通信连接
        WsServer_t::connection_ptr wconn = _online_manager->get_conn_from_room(_whitechess_id);
        WsServer_t::connection_ptr bconn = _online_manager->get_conn_from_room(_blackchess_id);
        if (wconn)
        {
            // 3.发送响应消息
            wconn->send(body, websocketpp::frame::opcode::text);
        }
        if (bconn)
        {
            bconn->send(body); // 默认就是以text文本类型发送
        }
    }

public:
    // 检查发送的消息是否有敏感词
    bool judge_msg(std::string &msg)
    {
        if (msg.find("妈") != std::string::npos)
        {
            return false;
        }
        if (msg.find("去你") != std::string::npos)
        {
            return false;
        }
        if (msg.find("死") != std::string::npos)
        {
            return false;
        }
        if (msg.find("去你") != std::string::npos)
        {
            return false;
        }
        if (msg.find("垃圾") != std::string::npos)
        {
            return false;
        }
        if (msg.find("废物") != std::string::npos)
        {
            return false;
        }
        if (msg.find("傻逼") != std::string::npos)
        {
            return false;
        }
        return true;
    }
    // 下棋子,并判断走这步棋子之后是否已经胜利
    bool judge_win(int uid, int row, int col)
    {
        // 1.判断该玩家是下白棋还是下黑棋
        int color;
        if (uid == _whitechess_id)
        {
            color = 1;
        }
        else
        {
            color = 2;
        }
        _gobang_board[row][col] = color; // 标记棋子

        // 2.判断是否出现五星连珠(有5个相同的颜色棋子相连)
        // 从当前位置开始的: y轴方向、x轴方向、正对角线方向、反对角线方向
        // 只要有一个方向出现五星连珠，就是胜利

        int sum = 1; // 当前位置棋子
        // 2.1 y轴正方向
        for (int i = row - 1; i >= 0; i--)
        {
            if (_gobang_board[i][col] == color)
                sum++;
            else
                break;
        }
        // 2.2 y轴负方向
        for (int i = row + 1; i <= BOARD_ROW - 1; i++)
        {
            if (_gobang_board[i][col] == color)
                sum++;
            else
                break;
        }
        // 判断是否胜利
        if (sum >= 5)
            return true;
        sum = 1;

        // 2.3 x轴正方向
        for (int j = col + 1; j <= BOARD_COL - 1; j++)
        {
            if (_gobang_board[row][j] == color)
                sum++;
            else
                break;
        }
        // 2.4 x轴负方向
        for (int j = col - 1; j >= 0; j--)
        {
            if (_gobang_board[row][j] == color)
                sum++;
            else
                break;
        }
        // 判断是否胜利
        if (sum >= 5)
            return true;
        sum = 1;

        // 2.5 正对角线的正方向
        for (int i = row - 1, j = col + 1; i >= 0 && j <= BOARD_COL - 1; i--, j++)
        {
            if (_gobang_board[i][j] == color)
                sum++;
            else
                break;
        }
        // 2.6 正对角线的反方向
        for (int i = row + 1, j = col - 1; i <= BOARD_ROW - 1 && j >= 0; i++, j--)
        {
            if (_gobang_board[i][j] == color)
                sum++;
            else
                break;
        }
        // 判断是否胜利
        if (sum >= 5)
            return true;
        sum = 1;

        // 2.7 反对角线的正方向
        for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--)
        {
            if (_gobang_board[i][j] == color)
                sum++;
            else
                break;
        }
        // 2.8 反对角线的反方向
        for (int i = row + 1, j = col + 1; i <= BOARD_ROW - 1 && j <= BOARD_COL - 1; i++, j++)
        {
            if (_gobang_board[i][j] == color)
                sum++;
            else
                break;
        }
        // 判断是否胜利
        if (sum >= 5)
            return true;

        return false;
    }

public: // 获取或设置房间内的一些信息
    int get_room_id()
    {
        return _room_id;
    }
    room_status get_room_status()
    {
        return _status;
    }
    int get_player_num()
    {
        return _players_num;
    }
    void set_white_userid(int uid)
    {
        _whitechess_id = uid;
        _players_num++;
    }
    void set_black_userid(int uid)
    {
        _blackchess_id = uid;
        _players_num++;
    }
    int get_white_userid()
    {
        return _whitechess_id;
    }
    int get_black_userid()
    {
        return _blackchess_id;
    }

private:
    int _room_id;                                // 房间ID
    room_status _status;                         // 房间状态
    int _players_num;                            // 玩家个数
    int _whitechess_id;                          // 白棋玩家ID;
    int _blackchess_id;                          // 黑棋玩家ID
    user_table *_user_table;                     // 指向用户表的句柄
    Online_Manager *_online_manager;             // 在线管理的句柄
    std::vector<std::vector<int>> _gobang_board; // 棋盘
};

using Room_ptr = std::shared_ptr<Room>;

class RoomManager
{
public:
    RoomManager(user_table *ut, Online_Manager *om)
        : _ut(ut), _om(om), _next_roomid(1)
    {
        LOG(LogLevel::INFO) << "房间管理类初始化成功";
    }
    ~RoomManager()
    {
    }

public:
    bool create_room(int uid1, int uid2)
    {
        // 两个用户在游戏大厅中匹配成功后才会创建房间,然后将两个用户加入到房间中

        // 1.检查两个用户是否都在游戏大厅,如果有哪个已经掉线了,就不需要创建房间了
        if (!_om->is_in_game_hall(uid1) || !_om->is_in_game_hall(uid2))
        {
            LOG(LogLevel::ERROR) << "有用户已经掉线,无法创建房间";
            return false;
        }

        {
            std::lock_guard<std::mutex> lock(_mutex);

            Room_ptr rp(new Room(_next_roomid, _ut, _om)); // 创建一个房间
            rp->set_white_userid(uid1);                    // 设置房间用户
            rp->set_black_userid(uid2);

            // 管理房间信息
            _roomid_to_ptr.insert(std::make_pair(_next_roomid, rp));
            _uid_to_roomid[uid1] = _next_roomid;
            _uid_to_roomid[uid2] = _next_roomid;

            _next_roomid++;
            return true;
        }
    }
    Room_ptr get_room_by_rid(int rid)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            auto it = _roomid_to_ptr.find(rid);
            if (it == _roomid_to_ptr.end())
            {
                return NULL;
            }
            return it->second;
        }
    }
    Room_ptr get_room_by_uid(int uid)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 先从_uid_to_roomid找到房间号
            auto it = _uid_to_roomid.find(uid);
            if (it == _uid_to_roomid.end())
            {
                return NULL;
            }
            int rid = it->second;

            // return get_room_by_rid(rid);   //注意: 不可以这样子写,因为这里的锁是互斥锁,是不可重入的,
            //                                调用get_room_by_rid(rid)内部还会申请锁,于是造成死锁！！

            auto it1 = _roomid_to_ptr.find(rid);
            if (it1 == _roomid_to_ptr.end())
            {
                return NULL;
            }
            return it1->second;
        }
    }

    void remove_room_by_rid(int rid)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 获取房间:
            Room_ptr rp = _roomid_to_ptr[rid];
            if (rp.get() == nullptr) // 获取智能指针指向的对象
            {
                return;
            }
            // 获取房间中所有用户的id:
            int uid1 = rp->get_white_userid();
            int uid2 = rp->get_black_userid();
            // 删除维护的用户id->房间id映射
            _uid_to_roomid.erase(uid1);
            _uid_to_roomid.erase(uid2);
            // 删除维护的房间id->房间映射
            _roomid_to_ptr.erase(rid);
        }
    }

    // 删除房间的指定用户,如果房间没有用户了,就销毁房间
    void remove_room_by_uid(int uid)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            // 先获取房间ID：
            int rid = _uid_to_roomid[uid];
            Room_ptr rp = _roomid_to_ptr[rid];
            // 该玩家退出房间
            rp->handle_exit(uid);
            // 如果房间玩家为0，销毁房间
            if (rp->get_player_num() == 0)
            {
                LOG(LogLevel::DEBUG) << "房间被销毁";
                _roomid_to_ptr.erase(rid);
            }
        }
    }

private:
    user_table *_ut;                                  // 指向用户表的句柄
    Online_Manager *_om;                              // 在线管理的句柄
    int _next_roomid;                                 // 房间id分配计数器
    std::unordered_map<int, Room_ptr> _roomid_to_ptr; // 房间id->房间
    std::unordered_map<int, int> _uid_to_roomid;      // 用户id->所处房间id

    std::mutex _mutex; // 保护计数器、两个哈希映射共享资源,防止在多线程下出现线程安全问题
};